package cn.itcast.search.service.impl;

import cn.itcast.feign.client.ItemClient;
import cn.itcast.hmall.dto.common.PageDTO;
import cn.itcast.hmall.dto.search.SearchReqDTO;
import cn.itcast.hmall.pojo.item.Item;
import cn.itcast.hmall.pojo.item.ItemDoc;
import cn.itcast.search.constants.IndexConstants;
import cn.itcast.search.service.SearchService;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.util.*;

@Service
@Slf4j
public class SearchServiceImpl implements SearchService {
    @Autowired
    private RestHighLevelClient client;
    @Autowired
    private ItemClient itemClient;

    /**
     * 搜索栏自动补全功能
     *
     * @param key 关键字
     * @return
     */
    @Override
    public List<String> suggestion(String key) {
        //1.创建请求对象
        SearchRequest request = new SearchRequest("item");

        //2.准备DSL
        request.source().suggest(
                new SuggestBuilder()
                        .addSuggestion("suggestion",
                                SuggestBuilders.completionSuggestion("suggestion") //建议字段
                                        .prefix(key) //前缀
                                        .size(10) //显示数量
                                        .skipDuplicates(true) //去重
                        ));

        //3.发送请求
        SearchResponse response = null;

        try {
            response = client.search(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("搜索栏自动补全功能失败   原因: {}", e.getMessage());
        }
        //4.解析结果
        ArrayList<String> list = new ArrayList<>();
        //4.1 获取建议对象
        Suggest suggest = response.getSuggest();
        CompletionSuggestion itemSuggestion = suggest.getSuggestion("suggestion");
        //4.2 获取建议列表
        List<CompletionSuggestion.Entry.Option> options = itemSuggestion.getOptions();
        //4.3 判断是否为空
        if (options == null || CollectionUtils.isEmpty(options)) {
            return list;
        }
        //4.4 遍历
        options.forEach(option -> {
            Text text = option.getText();
            list.add(text.string());
        });

        //5.返回结果
        System.out.println(list);
        return list;
    }

    /**
     * 过滤项聚合功能
     *
     * @param searchReqDTO 请求参数
     * @return
     */
    @Override
    public Map<String, List<String>> getFilter(SearchReqDTO searchReqDTO) {
        //1.创建请求对象
        SearchRequest request = new SearchRequest("item");

        //2.准备DSL
        //2.1基本查询
        buildBasicQuery(request, searchReqDTO);
        //2.2设置size
        request.source().size(0);
        //2.3设置聚合
        buildAggsQuery(request);

        //3.发送请求
        SearchResponse response = null;
        try {
            response = client.search(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("过滤项聚合功能失败   原因: {}", e.getMessage());
            throw new RuntimeException(e);
        }

        //4.解析结果
        HashMap<String, List<String>> bucketMap = new HashMap<>();
        //4.1 品牌聚合结果
        List<String> brandList = getBucketByName(response, "brandAgg");
        bucketMap.put("brand", brandList);
        //4.2 分类聚合结果
        List<String> categoryList = getBucketByName(response, "categoryAgg");
        bucketMap.put("category", categoryList);

        return bucketMap;
    }

    /**
     * 基本搜索功能
     *
     * @param searchReqDTO 请求参数
     * @return
     */
    @Override
    public PageDTO<ItemDoc> getItemList(SearchReqDTO searchReqDTO) {
        //1.创建请求对象
        SearchRequest request = new SearchRequest(IndexConstants.INDEX_NAME);

        //2.准备DSL
        //2.1基本查询
        buildBasicQuery(request, searchReqDTO);

        //2.2设置分页
        Integer page = searchReqDTO.getPage();
        Integer size = searchReqDTO.getSize();
        request.source().from((page-1)*size).size(size);

        //3.执行请求
        SearchResponse response = null;
        try {
            response = client.search(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("基本搜索功能失败   原因: {}", e.getMessage());
        }

        //4.解析结果
        if (response == null) {
            return null;
        }
        return parseResponse(response);
    }

    /**
     * 删除文档
     * @param id
     */
    @Override
    public void deleteDoc(Long id) {
        DeleteRequest request = new DeleteRequest(IndexConstants.INDEX_NAME, String.valueOf(id));

        try {
            client.delete(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("删除文档id为{} 时失败   原因: {}", id, e.getMessage());
            throw new RuntimeException(e);
        }
    }

    /**
     * 添加文档
     * @param id
     */
    @Override
    public void insertDoc(Long id) {
        //1.查询新增数据
        //查询数据
        Item item = itemClient.getOne(id);
        //封装文档数据
        ItemDoc itemDoc = new ItemDoc(item);
        //转换JSON
        String json = JSON.toJSONString(itemDoc);

        //2.创建请求对象
        IndexRequest request = new IndexRequest("item").id(String.valueOf(id));
        //添加文档数据
        request.source(json, XContentType.JSON);

        //3.发送请求
        try {
            client.index(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("添加文档id为:{} 时失败   原因: {}",id, e.getMessage());
            throw new RuntimeException(e);
        }


    }

    /**
     * 解析响应结果
     *
     * @param response 响应结果
     * @return
     */
    private PageDTO<ItemDoc> parseResponse(SearchResponse response) {
        //获取文档数据
        SearchHits hits = response.getHits();
        ArrayList<ItemDoc> itemDocList = new ArrayList<>();

        //获取数据总条数
        long total = hits.getTotalHits().value;

        //获取文档数组
        SearchHit[] hits1 = hits.getHits();

        Arrays.stream(hits1).forEach(hit -> {
            //获取文档数据
            String sourceAsString = hit.getSourceAsString();
            //反序列化
            ItemDoc itemDoc = JSON.parseObject(sourceAsString, ItemDoc.class);
            //高亮
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            HighlightField name = highlightFields.get("name");
            if (name != null){
                itemDoc.setName(name.getFragments()[0].string());
            }

            itemDocList.add(itemDoc);

        });
        return new PageDTO<>(total, itemDocList);
    }

    /**
     * 解析聚合结果
     *
     * @param response 响应结果
     * @param key      聚合关键字
     * @return
     */
    private List<String> getBucketByName(SearchResponse response, String key) {

        Aggregations aggregations = response.getAggregations();

        //根据名称获取聚合结果
        Terms aggregation = aggregations.get(key);

        //获取桶
        List<? extends Terms.Bucket> buckets = aggregation.getBuckets();

        //遍历桶
        ArrayList<String> list = new ArrayList<>();
        buckets.forEach(bucket -> {
            String keyAsString = bucket.getKeyAsString();
            list.add(keyAsString);
        });

        return list;
    }

    /**
     * 构建聚合查询
     *
     * @param request
     */
    private void buildAggsQuery(SearchRequest request) {
        //品牌聚合
        request.source().aggregation(
                AggregationBuilders
                        .terms("brandAgg")
                        .field("brand")
                        .size(20)
        );

        //分类聚合
        request.source().aggregation(
                AggregationBuilders
                        .terms("categoryAgg")
                        .field("category")
                        .size(20)
        );

    }

    /**
     * 构建基本查询
     */
    private void buildBasicQuery(SearchRequest request, SearchReqDTO searchReqDTO) {

        // 1.构造过滤条件

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        //关键字过滤
        if (StringUtils.isEmpty(searchReqDTO.getKey())) {
            //匹配所有
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());
        }else {
            //匹配关键字
            boolQueryBuilder.must(QueryBuilders.matchQuery("all", searchReqDTO.getKey()));
        }

        //分类过滤
        if (!StringUtils.isEmpty(searchReqDTO.getCategory())){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category", searchReqDTO.getCategory()));
        }

        //品牌过滤
        if (!StringUtils.isEmpty(searchReqDTO.getBrand())){
            boolQueryBuilder.filter(QueryBuilders.termQuery("brand", searchReqDTO.getBrand()));
        }

        //价格过滤
        if (searchReqDTO.getMinPrice() != null && searchReqDTO.getMaxPrice() != null){
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(searchReqDTO.getMinPrice()).lte(searchReqDTO.getMaxPrice()));
        }

        //2.算分控制
        FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(
                //原始查询
                boolQueryBuilder,
                //函数过滤
                new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                        //按权重分进行排序
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                //匹配isAD字段
                                QueryBuilders.termQuery("isAD", true),
                                //设置权重
                                ScoreFunctionBuilders.weightFactorFunction(10)
                        )
                }

        );

        //3.高亮展示
        request.source().highlighter(
                new HighlightBuilder()
                        .field("name")
                        .requireFieldMatch(false)//不需要根据高亮字段进行加粗
        );

        //设置查询字段
         request.source().query(functionScoreQueryBuilder);
    }
}
